package com.trgis.yhmap.util.wmtsproxy;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


import com.trgis.yhmap.util.wmtsproxy.exception.ExceptionMsg;
import com.trgis.yhmap.util.wmtsproxy.exception.WmtsConfigException;
import com.trgis.yhmap.util.wmtsproxy.vo.LayersVO;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

/**
 * 2015年10月10日
 * @author wangfy
 * 因GeoServer发布的瓦片WMTS服务接口参数tileMatrix的值与天地图浙江的值（层数）不同，
 * 为与浙江保持一致，特写此代理进行对应转换
 * 浙江天地图wmts的请求参数一般为：</br>
 * wmts?TILEMATRIXSET=TileMatrixSet0&
 * ......&<b>TileMatrix={TileMatrix}</br>&
 * TileRow={row}&TileCol={col}</br>
 * 其中TileMatrix的值为1--20，即瓦片的层级</br>
 * 2016年6月22日 wangfy 修改了参数 tileMatrixSet 大小写错误所造成的问题</br>
 * 2017年9月20日 wangfy 增加瓦片范围判断，防止无效请求</br>
 * 2018年8月8日  wangfy 给方法加上了线程安全锁，修复了地址参数可能拼接错误的问题
 * 2019年3月25日 wangfy 修复了一个错误，在没有配置limits的情况下，没有取对应的TileMatrix
 * 2020年11月14日 wangfy 修复代理地址中加入token等参数时不能&拼接参数的问题
 * 2021年2月9日 wangfy 增加了根据服务url获取capabilities内容
 * 2021年5月28日 wangfy 增加配置图层名称与代理名称可以不同的功能
 * 2021年10月9日 wangfy 修复了获取图层tileMatrix只能是数字的错误
 * 2021年12月15日 wangfy 增加读取并替换ResourceURL template和获取Rest风格瓦片地址的功能
 */
public class WmtsXMLReader {
	private static final Logger log = LoggerFactory.getLogger(WmtsXMLReader.class);
	private static WmtsXMLReader xr = new WmtsXMLReader();

	private Element providerInfo;
	
	/**
	 * wmts请求中图层参数
	 */
	private final static String LAYER = "LAYER";
	
	/**
	 * wmts请求中瓦片的层级参数
	 */
	private final static String TILEMATRIX = "TILEMATRIX";
	
	/**
	 * wmts请求中瓦片集使用的坐标系参数
	 */
	private final static String TILEMATRIXSET = "TILEMATRIXSET";
	
	private final static String TILEROW = "TILEROW";
	private final static String TILECOL = "TILECOL";
	
	/**
	 * wmtsCon.xml配置文件中的格式
	 */
	private final static String ATTR_NAME = "name";
	private final static String ATTR_URL = "url";
	private final static String ATTR_KEY = "key";
	private final static String ATTR_VALUE = "value";
	private final static String ATTR_FILE = "filePath";
	private final static String NODE_LAYER = "layer";
	private final static String NODE_MATRIXSET = "matrixSet";
	private final static String NODE_MATRIX = "matrix";
	
	private WmtsXMLReader() {}

	public static WmtsXMLReader getReader(){
		return xr;
	}
	
	/**
	 * 读取到的配置文件内容
	 */
	private Map<String, Wmts> wmtsLayers = new HashMap<>();
	
	private class Wmts{
		private final Pattern p = Pattern.compile("<TileMatrixLimits>[\\s\\S]+?</TileMatrixLimits>");
		private final Pattern p0 = Pattern.compile("<TileMatrix>[\\d\\s\\S]+?</TileMatrix>");
		private final Pattern p1 = Pattern.compile("<MinTileRow>\\d+</MinTileRow>");
		private final Pattern p2 = Pattern.compile("<MaxTileRow>\\d+</MaxTileRow>");
		private final Pattern p3 = Pattern.compile("<MinTileCol>\\d+</MinTileCol>");
		private final Pattern p4 = Pattern.compile("<MaxTileCol>\\d+</MaxTileCol>");
		private final Pattern pd = Pattern.compile("\\d+");
		/**
		 * 全局配置环境下的图层名称
		 */
		protected String name;
		protected String url;
		/**
		 * 真实的图层名称，在真实调用时需要替换成真实的图层名称
		 */
		protected String layerName;
		/**
		 * 配置文件中的TileMatrixSet
		 */
		protected String tileMatrixSetKey;
		/**
		 * 真实地址中的TileMatrixSet
		 */
		protected String tileMatrixSetValue;
		
		private String xmlContent;
		
		/**
		 * 天地图的瓦片层级跟GeoServer中发布的图层层级对应关系
		 * 如 key “10”对应 value "EPSG:4490_wfytestarcgis:0"
		 */
		protected Map<String, String> tileMatrixs = new HashMap<>();
		
		/**
		 * 存放瓦片层级和行列号的范围对应关系，瓦片的配置文件中每一级的对应如下</br>
		 * <TileMatrix>19</TileMatrix>
		 * <MinTileRow>86558</MinTileRow>
		 * <MaxTileRow>87145</MaxTileRow>
		 * <MinTileCol>436436</MinTileCol>
		 * <MaxTileCol>437403</MaxTileCol></br>
		 * key：19，value：[86558,87145,436436,437403]
		 */
		protected Map<String, int[]> tileMatrixLimits = new HashMap<>();
		
		protected Wmts(String name, String url){
			this.name = name;
			this.layerName = name;
			this.url = url;
			if (!this.url.endsWith("?")){
				if (!url.contains("?")){
					this.url += "?";
				} else {
				    this.url += "&";
                }
			}
		}

		protected String getMatrixSetKeyFromValue(String value){
			for (Iterator<Map.Entry<String, String>> it = tileMatrixs.entrySet().iterator(); it.hasNext(); ){
				Map.Entry<String, String> entry = it.next();
				if (entry.getValue().equals(value)) {
					return entry.getKey();
				}
			}
			return null;
		}
		
		/**
		 * 2017年9月20日 wangfy 增加瓦片范围判断，防止无效请求
		 * @param content
		 */
		protected void setXmlContent(String content){
//			log.info("设置图层 {} 的配置信息", this.name);
			this.xmlContent = content;
			
			Matcher m = p.matcher(this.xmlContent);
			String str;
			while(m.find()){
				str = m.group();
				Matcher m0 = p0.matcher(str);
				Matcher m1 = p1.matcher(str);
				Matcher m2 = p2.matcher(str);
				Matcher m3 = p3.matcher(str);
				Matcher m4 = p4.matcher(str);
				Matcher md;
				int[] nums = {0,0,0,0};
				if(m0.find()){
				    String temp = m0.group();
                    tileMatrixLimits.put(temp.substring(temp.indexOf(">") + 1, temp.lastIndexOf("<")), nums);
				} else {
					continue;
				}
				if(m1.find()){
					md = pd.matcher(m1.group());
					if(md.find()){
						nums[0] = Integer.parseInt(md.group());
					}
				}
				if(m2.find()){
					md = pd.matcher(m2.group());
					if(md.find()){
						nums[1] = Integer.parseInt(md.group());
					}
				}
				if(m3.find()){
					md = pd.matcher(m3.group());
					if(md.find()){
						nums[2] = Integer.parseInt(md.group());
					}
				}
				if(m4.find()){
					md = pd.matcher(m4.group());
					if(md.find()){
						nums[3] = Integer.parseInt(md.group());
					}
				}
			}
		}
		
        /**
         *
         * @param level
         * @param tileRow
         * @param tileCol
         * @return
         * @throws WmtsConfigException
         */
		private void checkIndex(String level, int tileRow, int tileCol) throws WmtsConfigException{
            int[] nums = tileMatrixLimits.get(level);
            if (null == nums)
                return;
            if(nums[0] > tileRow)
                throw new WmtsConfigException(ExceptionMsg.tileOutOfRange, "tileRow " + tileRow + " is out of range, "
                        + "level: " + level + " min: " + wmts.tileMatrixLimits.get(level)[0] + " max: " + wmts.tileMatrixLimits.get(level)[1]);
            if(nums[1] < tileRow)
                throw new WmtsConfigException(ExceptionMsg.tileOutOfRange, "tileRow " + tileRow + " is out of range, "
                        + "level: " + level + " min: " + wmts.tileMatrixLimits.get(level)[0] + " max: " + wmts.tileMatrixLimits.get(level)[1]);
            if(nums[2] > tileCol)
                throw new WmtsConfigException(ExceptionMsg.tileOutOfRange, "tileCol " + tileCol + " is out of range, "
                        + "level: " + level + " min: " + wmts.tileMatrixLimits.get(level)[2] + " max: " + wmts.tileMatrixLimits.get(level)[3]);
            if(nums[3] < tileCol)
                throw new WmtsConfigException(ExceptionMsg.tileOutOfRange, "tileCol " + tileCol + " is out of range, "
                        + "level: " + level + " min: " + wmts.tileMatrixLimits.get(level)[2] + " max: " + wmts.tileMatrixLimits.get(level)[3]);
            return;
        }
	}
	
	private Wmts wmts = null;


	/**
	 * 从wmts的请求参数：图层名称和matrix，得到对应的服务地址和matrix
	 * 2018年8月8日 给方法加上了线程安全锁，修复了地址参数可能拼接错误的问题
	 * @param request
	 * @return 返回正确的瓦片地址
	 * @throws WmtsConfigException 
	 */
	public synchronized String getUrl(final String layer, final HttpServletRequest request) throws WmtsConfigException {
		wmts = wmtsLayers.get(layer);

		if (!Optional.ofNullable(wmts).isPresent()){
		    throw new WmtsConfigException(ExceptionMsg.notFoundLayer, "没找到图层 " + layer);
        }

		Map<String, String[]> theParams = request.getParameterMap();
		String tileLevel = "0", tileRow = "0", tileCol = "0";
		
		StringBuilder newQueryStr = new StringBuilder();
		for (String key : theParams.keySet()){
			switch (key.toUpperCase()){
				case LAYER :
					if(!wmts.layerName.equals(wmts.name)){
						theParams.get(key)[0] = wmts.layerName;
					}
					break;
				case TILEMATRIXSET :
					// 判断请求中的参数跟配置中的 一样不， 配置里有的话，参数必须跟跟配置的一致，配置里没有的话不做替换
					if (null != wmts.tileMatrixSetKey)
						if (theParams.get(key)[0].equalsIgnoreCase(wmts.tileMatrixSetKey) )
							theParams.get(key)[0] = wmts.tileMatrixSetValue;
						else
							throw new WmtsConfigException(ExceptionMsg.notFoundMatrix, theParams.get(key)[0]);
					break;
				case TILEMATRIX:
					tileLevel = theParams.get(key)[0];
					// 如果配置中有相关层级对应关系，如果没有配置任何层级关系对应就不做替换
					// 2019年3月25日 修复limits为空时，没有取对应的tileMatrix
					if (!wmts.tileMatrixs.isEmpty() && wmts.tileMatrixs.containsKey(tileLevel)) {
						theParams.get(key)[0] = wmts.tileMatrixs.get(tileLevel);
					}
					break;
				case TILEROW :
					tileRow = theParams.get(key)[0];
					break;
				case TILECOL :
					tileCol = theParams.get(key)[0];
					break;
				default:
					break;
			}
		    
			newQueryStr.append(key).append("=").append(theParams.get(key)[0]).append("&");
		}
		wmts.checkIndex(tileLevel, Integer.parseInt(tileRow), Integer.parseInt(tileCol));

		newQueryStr.deleteCharAt(newQueryStr.length() - 1);
		
		return wmts.url + newQueryStr.toString();
	}

    /**
     * 2021年12月15日 获取真实的瓦片请求地址
     * @param layer
     * @param style
     * @param tileMatrixSet
     * @param tileMatrix
     * @param tileRow
     * @param tileCol
     * @param format
     * @return
     * @throws WmtsConfigException
     */
    public synchronized String getRestUrl(String layer, String style,
                                      String tileMatrixSet, String tileMatrix,
                                      int tileRow, int tileCol, String format) throws WmtsConfigException {
	    // http://127.0.0.1:8080/geoserver/gwc/service/wmts/rest/lpemapanno/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=image/png
        wmts = wmtsLayers.get(layer);
        if (null == wmts) throw new WmtsConfigException(ExceptionMsg.notFoundLayer, "没有该图层：" + layer);
        // 判断范围
        String level = (!wmts.tileMatrixs.isEmpty() && wmts.tileMatrixs.containsKey(tileMatrix)) ? wmts.tileMatrixs.get(tileMatrix) : tileMatrix;
        wmts.checkIndex(level, tileRow, tileCol);

        String[] confUrl = wmts.url.split("\\?");
        StringBuilder url = new StringBuilder();
        url.append(confUrl[0]).append("/rest/").append(wmts.layerName).append("/").append(style).append("/");
        if (tileMatrixSet.equalsIgnoreCase(wmts.tileMatrixSetKey)) {
            url.append(wmts.tileMatrixSetValue).append("/");
        } else {
            url.append(tileMatrixSet).append("/");
        }
        url.append(level).append("/");
        url.append(tileRow).append("/").append(tileCol);
        url.append("?format=").append(format);
        if (confUrl.length > 1) {
            url.append("&").append(confUrl[1]);
        }
        return url.toString();
    }

	/**
	 * 读取wmtsconf 配置文件内容
	 * @param file
	 * @throws DocumentException
	 */
	public void readConfig(final File file) throws DocumentException{
		log.info("读取配置文件");
		SAXReader reader = new SAXReader();
		Document doc = reader.read(file);
		Element rootElement = doc.getRootElement();
		this.providerInfo = rootElement.element("ServiceProvider");
		List<Element> lys = rootElement.elements(NODE_LAYER);
		int size = (int) (lys.size() / 0.75f) + 1;
		wmtsLayers = new HashMap<>(size);
		String layName = "";
		Wmts wmts;
		for (Element el : lys) {
			layName = el.attributeValue(ATTR_NAME);
			String realLayerName = el.attributeValue(ATTR_VALUE);
			wmts = new Wmts(layName, el.attributeValue(ATTR_URL));
			if (Optional.ofNullable(realLayerName).isPresent()) {
				log.info("读取配置图层 {} ，原名称: {}", layName, realLayerName);
				wmts.layerName = realLayerName;
			} else {
				log.info("读取配置图层 {}", layName);
			}

			String attrFile = el.attributeValue(ATTR_FILE);
			if(null != attrFile && !"".equals(attrFile) && !"null".equalsIgnoreCase(attrFile))
				readCapaFile(wmts, file.getParent() + File.separator + attrFile);
			wmtsLayers.put(layName, wmts);
			Element mset = el.element(NODE_MATRIXSET);
			if (null != mset) {
				//可以只配置key <matrixSet key="TileMatrixSet0">
				wmts.tileMatrixSetKey = mset.attributeValue(ATTR_KEY);

				String tilems = mset.attributeValue(ATTR_VALUE);
				if (null == tilems) {
				    tilems = wmts.tileMatrixSetKey;
                }
				wmts.tileMatrixSetValue = tilems;
				List<Element> ms = mset.elements(NODE_MATRIX);
				for (Element m : ms) {
                    if (!Optional.ofNullable(m.attributeValue(ATTR_VALUE)).isPresent()) {
                        wmts.tileMatrixs.put(m.attributeValue(ATTR_KEY), m.attributeValue(ATTR_KEY));
                    } else {
                        wmts.tileMatrixs.put(m.attributeValue(ATTR_KEY), m.attributeValue(ATTR_VALUE));
                    }
				}
			}
		}

	}
	
	/**
	 * 读取wmts服务图层的capabilities内容，并获取层级和行列号的范围
	 * @param wmts
	 * @param filePath
	 */
	private void readCapaFile(final Wmts wmts, final String filePath){
		if(null == filePath){
			return;
		}
		Runnable thread = () -> {
			try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"UTF-8"))) {
				StringBuilder sb = new StringBuilder();
				String line = br.readLine();
				while(null != line){
					sb.append(line).append("\n");
					line = br.readLine();
				}
                wmts.setXmlContent(sb.toString());
			} catch (IOException e) {
			    log.error("读取图层 {} 的配置文件时出错： {}", wmts.name, e.getMessage());
				e.printStackTrace();
			}
        };
		thread.run();
	}
	
	public String getCapabilities(final String layerName) throws WmtsConfigException{
		Wmts wmts = wmtsLayers.get(layerName);
		if(null == wmts) throw new WmtsConfigException(ExceptionMsg.notFoundLayer);
		if(null == wmts.xmlContent) throw new WmtsConfigException(ExceptionMsg.notFoundCapabilities);
		return wmts.xmlContent;
	}

	public String getServerUrl(String layer){
        Wmts wmts = wmtsLayers.get(layer);
        return wmts.url;
    }

    public String gerRealLayer(String layer){
		return wmtsLayers.get(layer).layerName;
	}

	/**
	 * 从真实地址获取图层参数信息后，用配置中的信息替换读取到的相关内容，主要替换matrixset等
	 * @param layer
	 * @param doc
	 * @param proxyUrl 本代理的地址，用来替换获取到的服务的真实地址
	 * @return
	 */
	public String changeSettings(String layer, final Document doc, String proxyUrl) throws IOException {
//		log.info("修改相关配置内容");
		Wmts wmts = wmtsLayers.get(layer);
		Element rootElement = doc.getRootElement();

		// 替换配置ServiceProvider节点信息
		Optional.ofNullable(rootElement.element("ServiceProvider")).ifPresent(p -> {
			for(Iterator iterator = p.elements().iterator(); iterator.hasNext();){
				iterator.next();
				iterator.remove();
			}
			Optional.ofNullable(getReader().providerInfo).ifPresent(info -> {
				p.addElement("company").setText(info.elementText("company"));
				p.addElement("name").setText(info.elementText("name"));
				p.addElement("qq").setText(info.elementText("qq"));
				p.addElement("phone").setText(info.elementText("phone"));
			});
		});

		// 删除GetFeatureInfo节点
		List<Element> operationsMetadata = rootElement.element("OperationsMetadata").elements("Operation");
		for(Iterator<Element> iterator = operationsMetadata.iterator(); iterator.hasNext();){
			Element operation = iterator.next();
			if ("GetFeatureInfo".equalsIgnoreCase(operation.attributeValue("name"))){
				iterator.remove();
			} else {
				Optional.ofNullable(operation.element("DCP")).ifPresent(
						dcp -> Optional.ofNullable(dcp.element("HTTP")).ifPresent(
								http -> Optional.ofNullable(http.element("Get")).ifPresent(
										get -> Optional.ofNullable(get.attribute("href")).ifPresent(
												href -> href.setValue(proxyUrl))
								)));
			}
		}
        int i = 0;
        for (Iterator<Element> iterator = rootElement.elementIterator("ServiceMetadataURL"); iterator.hasNext(); i++) {
            Element url = iterator.next();
            url.attribute("href").setValue(proxyUrl);
            if (i > 0) iterator.remove();
        }

		rootElement.element("Contents").elements("Layer").stream().filter(lay ->
			lay.element("Identifier").getText().equals(wmts.name)
		).findAny().ifPresent(lay -> {
		    lay.elements("TileMatrixSetLink").forEach(link -> {
                Element tileSet = link.element("TileMatrixSet");
                if (tileSet.getText().equals(wmts.tileMatrixSetValue)) {
                    tileSet.setText(wmts.tileMatrixSetKey);
                    link.element("TileMatrixSetLimits").elements("TileMatrixLimits").forEach(tl -> {
                        String lv = tl.element("TileMatrix").getText();
                        if (wmts.tileMatrixs.containsValue(lv)) {
                            tl.element("TileMatrix").setText(wmts.getMatrixSetKeyFromValue(lv));
                        }
                    });
                }
            });
            for (Iterator<Element> iterator = lay.elements("ResourceURL").iterator(); iterator.hasNext(); ) {
                Element resUrl = iterator.next();
                String resType = resUrl.attributeValue("resourceType");
                if ("FeatureInfo".equalsIgnoreCase(resType)) {
                    iterator.remove();
                } else if ("tile".equalsIgnoreCase(resType)) {
                    // 替换真实的地址
                    String template = resUrl.attributeValue("template");
                    // http://127.0.0.1:8080/geoserver/gwc/service/wmts/rest/lpemapanno/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=image/png
                    String[] realUrl = wmts.url.split("\\?");
                    template = template.replace(realUrl[0], proxyUrl);
                    template = template.replace("/" + wmts.name + "/rest/" + wmts.layerName, "/rest/" + wmts.name);
                    if (realUrl.length > 1) {
                        template = template + "&" + realUrl[1];
                    }
                    if (template.endsWith("&")) {
                        template = template.substring(0, template.length() - 1);
                    }
                    resUrl.attribute("template").setValue(template);
                }
            }
        });

		rootElement.element("Contents").elements("TileMatrixSet").forEach(set -> {
			if (set.element("Identifier").getText().equals(wmts.tileMatrixSetValue)){
				set.element("Identifier").setText(wmts.tileMatrixSetKey);
				set.elements("TileMatrix").forEach(matrix -> {
					String lv = matrix.element("Identifier").getText();
					if (wmts.tileMatrixs.containsValue(lv)){
						matrix.element("Identifier").setText(wmts.getMatrixSetKeyFromValue(lv));
					}
				});
			}
		});

		StringWriter stringWriter = new StringWriter();
		doc.write(stringWriter);
		// 删除多余的空行
		wmts.setXmlContent(stringWriter.toString().replaceAll("(?m)^\\s*$(\\n|\\r\\n)",""));
		return wmts.xmlContent;
	}

	/**
	 * 获取所有的图层服务
	 * @return
	 */
	public LayersVO getAllLayers(){
		LayersVO layersVO = new LayersVO();
		layersVO.setLayers(wmtsLayers.keySet());
		return layersVO;
	}
}

